package starfield.modpack.web;

import starfield.modpack.web.SyncingException;
import starfield.modpack.web.SyncingTask;
import starfield.modpack.web.WebModpack;
import starfield.modpack.Modpack;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.logging.Level;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.validator.routines.UrlValidator;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.DateUtils;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class HTTPSyncingTask extends SyncingTask {

	public HTTPSyncingTask(WebModpack webModpack, boolean silent) {
		super(webModpack, silent);
	}
	
	@Override
	protected Void doInBackground() throws IOException, URISyntaxException, SyncingException, InterruptedException {
				
		if (!new UrlValidator(new String[] { "http" }).isValid(modpack.getHostname())) {
			throw new SyncingException("Hostname must be a valid HTTP URL.");
		}
		
		URI uri = new URIBuilder(modpack.getHostname()).setPort(modpack.getPort()).build();
		
		publish("Connecting to '" + uri + "'...", Level.INFO);
		
		try (CloseableHttpClient client = HttpClients.createDefault()) {
			HttpHost host = new HttpHost(uri.getHost(), uri.getPort());
			HttpClientContext context = HttpClientContext.create();
			
			if (modpack.requiresLogin()) {
				UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(modpack.getUsername(), modpack.getPassword());
				CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
				credentialsProvider.setCredentials(new AuthScope(host), credentials);
				context.setCredentialsProvider(credentialsProvider);
			}
			
			HttpGet listRequest = new HttpGet(uri.getPath());
			
			try (CloseableHttpResponse listResponse = client.execute(host, listRequest, context)) {
				int listResponseCode = listResponse.getStatusLine().getStatusCode();
				
				if (listResponseCode == 200) {
					publish("Connection successful.", Level.FINE);
				} else if (listResponseCode == 401) {
					if (modpack.requiresLogin()) {
						throw new SyncingException("Invalid login credentials.");
					} else {
						throw new SyncingException("Server requires login, please specify credentials.");
					}
				} else if (listResponseCode == 404) {
					throw new SyncingException("Requested URL was not found.");
				} else {
					throw new SyncingException("Unknown response code '" + listResponseCode + "'.");
				}
				
				HttpEntity listEntity = listResponse.getEntity();
				
				if (listEntity == null) {
					throw new SyncingException("Response entity is null.");
				}
				
				Document document = Jsoup.parse(EntityUtils.toString(listEntity), uri.toString());
				
				publish("Parsed response as HTML, looking for valid links...", Level.INFO);
				
				Elements links = document.select("a[href]");
				
				for (Element link : links) {
					String absoluteUrl = link.absUrl("href");
					String remoteFileName = FilenameUtils.getName(absoluteUrl);
					
					if (Modpack.isModpackFilename(remoteFileName)) {
						publish("Found valid modpack file: '" + remoteFileName + "'.", Level.INFO);
						
						File localFile = new File(modpack.getDirectoryPath() + File.separator + remoteFileName);
						boolean localFileExists = localFile.exists() && localFile.isFile();
						Date localDate = new Date(localFile.lastModified());
						
						HttpGet downloadRequest = new HttpGet(absoluteUrl);
						
						try (CloseableHttpResponse downloadResponse = client.execute(host, downloadRequest, context)) {
							int downloadResponseCode = downloadResponse.getStatusLine().getStatusCode();
							
							if (downloadResponseCode == 200) {
								boolean download = false;
								
								if (!localFileExists) {
									publish("Downloading file '" + remoteFileName + "'...", Level.INFO);
									download = true;
								} else {
									Header lastModifiedHeader = downloadResponse.getFirstHeader("last-modified");
									
									if (lastModifiedHeader != null) {
										Date remoteDate = DateUtils.parseDate(lastModifiedHeader.getValue());
										
										if (remoteDate.after(localDate)) {
											publish("Updating file '" + remoteFileName + "'...", Level.INFO);
											download = true;
										} else {
											publish("Local file '" + localFile.getName() + "' is up to date.", Level.INFO);
										}
									} else {
										// TODO: Should be an option to download when unsure
										publish("Could not determine last modified date of file '" + remoteFileName + "', downloading...", Level.INFO);
										download = true;
									}
								}
								
								if (download) {
									HttpEntity downloadEntity = downloadResponse.getEntity();
									
									if (downloadEntity != null) {
										try (FileOutputStream fos = new FileOutputStream(localFile)) {
											downloadEntity.writeTo(fos);
										}
										
										publish("File transfer successful.", Level.FINE);
									} else {
										publish("File transfer failed: Reponse entity is null.", Level.WARNING);
									}
								}
							} else {
								publish("File transfer failed: " + downloadResponse.getStatusLine().getReasonPhrase() + " (" + downloadResponseCode + ")", Level.WARNING);
							}
						}
					}
				}
			
				publish("Looking for obsolete files to delete...", Level.INFO);

				for (File localFile : modpack.getDirectory().listFiles()) {
					boolean remoteFileExists = false;

					for (Element link : links) {
						String remoteFileName = FilenameUtils.getName(link.absUrl("href"));
						
						if (remoteFileName.equals(localFile.getName())) {
							remoteFileExists = true;
							break;
						}
					}

					if (!remoteFileExists) {
						localFile.delete();
						publish("Deleted obsolete file '" + localFile.getName() + "'.", Level.INFO);
					}
				}

				publish("Disconnecting...", Level.INFO);
			}
		}
		
		publish("Sync successful.", Level.FINE);
		
		return null;
	}
}
